import copy
import json
import datetime
from base import loginfo
from base.testcase import TestCase
from base.test_data import TestData
from base.base_request import ApiRequests
import jsonpath
import re
import traceback
from base.setting import setting


class TestRunner(TestCase):
    """
        测试运行类,主要实现用例的运行，记录运行过程，输出测试报告数据--对接html的json数据
    """
    rq = ApiRequests()
    case_obj_list = None
    scene = True
    #  这里增加递增变量进入存储空间
    save_space = {}
    test_result = {'testPass': 0, 'testFail': 0}

    def run_test(self):
        """
            用例运行器
        """
        start_time = datetime.datetime.now()
        t = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        if not self.scene:
            """
             执行单接口用例--暂时被废弃，所有用例均组成场景执行
            """
            for case in self.case_obj_list:
                loginfo.debug('开始执行接口测试用例:  {}'.format(case.name))
                self.before_parameter(case, self.save_space)
                try:
                    case.actual = self.rq.run_main(url=case.api_address, data=case.request_data,
                                                   method=case.method)
                    if len(str(case.actual)) > 500:
                        loginfo.debug('返回结果: \n\t\t\t\t\t\t{}'.format(str(case.actual)[:501] + '...'))
                    else:
                        loginfo.debug('返回结果: \n\t\t\t\t\t\t{}'.format(case.actual))
                    case.api_assert()
                    if not case.case_pass:
                        raise Exception('断言失败: 期望结果 {} 中没有找到对应的数据'.format(case.expected))
                except Exception as e:
                    loginfo.debug('{} 用例运行失败,失败信息\n\t\t\t\t\t\t{}'.format(case.name, e))
                    case.case_pass = False
                else:
                    case.case_pass = True
                    self.save_middle_result(case.save_field, case.actual)
                    self.after_parameter(case)
                finally:
                    loginfo.debug('测试结果: {}'.format(case.case_pass))
        else:
            """
                执行场景口用例
            """
            for t_index, scenes in enumerate(self.case_obj_list):
                self.save_space.update(setting.seq_env)
                scenes_name = list(scenes.keys())[0]
                loginfo.debug('\033[1;34m场景测试用例:  {},包含用例{}\033[0m'.format(scenes_name,
                                                                           [i.name for i in
                                                                            scenes[list(scenes.keys())[0]]]))
                for index, case in enumerate(scenes[list(scenes.keys())[0]]):
                    loginfo.debug(
                        '\033[1;29m执行用例:  {}.{} {}---{}\033[0m'.format(t_index + 1, index + 1, scenes_name, case.name))
                    try:
                        if 'python' in case.name:
                            exec(case.api_address)
                        else:
                            self.replace_data(case, self.save_space)
                            self.before_parameter(case, self.save_space)
                            case.actual = self.rq.run_main(url=case.api_address, data=case.request_data,
                                                           method=case.method)
                            self.after_parameter(case)
                            if len(str(case.actual)) > 500:
                                loginfo.debug('返回结果: \n\t\t\t\t\t\t{}'.format(str(case.actual)[:501] + '...'))
                            else:
                                loginfo.debug('返回结果: \n\t\t\t\t\t\t{}'.format(case.actual))
                            case.api_assert()
                            if not case.case_pass:
                                raise Exception('断言失败: 期望结果 {} 中没有找到对应的数据'.format(case.expected))
                    except Exception as e:
                        loginfo.debug('{} 用例运行失败,失败信息\n\t\t\t\t\t\t{}'.format(case.name, e))
                        traceback.print_exc()
                        case.case_pass = False
                        case.logs = traceback.format_exc().replace('\n', '\\n98765').split('98765')
                    else:
                        case.case_pass = True
                        if case.save_field:
                            case.save = self.save_middle_result(case.save_field, case.actual)
                            self.save_space.update(case.save)
                    finally:
                        if 'start' in self.save_space and 'step' in self.save_space:
                            self.save_space['start'] = str(int(self.save_space['start']) + int(self.save_space['step']))
                        loginfo.debug('\033[1;33m测试结果: {}\033[0m\n'.format(case.case_pass))
                # 中间结果数据清理
                self.save_space.clear()
        # 统计测试结果
        end_time = datetime.datetime.now() - start_time
        self.test_result['start_time'] = t
        self.test_result['end_time'] = end_time.seconds
        self.generate_result_data(self.case_obj_list)
        # 生成测试报告
        TestReport().create_report()

    def before_parameter(self, case, mid_res):
        """
            可对请求发送前进行参数处理
        """
        pass

    def after_parameter(self, res):
        """
            请求后进行参数的处理
        """
        pass

    def save_middle_result(self, save_filed, res):
        """
            中间结果的保存
        """
        import json
        if ',' in save_filed:
            saves = {}
            for i in save_filed.split(','):
                save_key, save_value = i[:i.find('=')], i[i.find('=') + 1:]
                save_data = jsonpath.jsonpath(json.loads(json.dumps(res)), save_value)
                if save_data:
                    if len(save_data) == 1:
                        saves.update({save_key: save_data[0]})
                    else:
                        saves.update({save_key: save_data})
            return saves
        else:
            save_key, save_value = save_filed[:save_filed.find('=')], save_filed[save_filed.find('=') + 1:]
            save_data = jsonpath.jsonpath(json.loads(json.dumps(res)), save_value)
            if save_data:
                if len(save_data) == 1:
                    return {save_key: save_data[0]}
                else:
                    return {save_key: save_data}

    def replace_data(self, case, save_space):
        """
            对参数进行替换
        """
        import json
        if '$' in case.api_address:
            s_key = re.findall('\$\{(.*?)\}', case.api_address)
            if len(s_key) == 1:
                case.api_address = case.api_address.replace('${' + s_key[0] + '}', str(save_space[s_key[0]]))
            elif len(s_key) > 1:
                mod_parameter = case.api_address
                for i in s_key:
                    if i in save_space:
                        mod_parameter = mod_parameter.replace('${' + i + '}', save_space[i])
                case.case.api_address = mod_parameter
            else:
                return case.request_data
        elif None in [case.request_data, save_space]:
            return case.request_data
        elif 'auto' in case.api_address:
            return case.request_data
        else:
            str_req = json.dumps(case.request_data)
            s_key = re.findall('\$\{(.*?)\}', str_req)
            if len(s_key) == 1 and s_key[0] in save_space:
                case.request_data = json.loads(str_req.replace('${' + s_key[0] + '}', str(save_space[s_key[0]])))
            elif len(s_key) > 1:
                mod_parameter = str_req
                for i in s_key:
                    if i in save_space:
                        mod_parameter = mod_parameter.replace('${' + i + '}', save_space[i])
                case.request_data = json.loads(mod_parameter)
            else:
                return case.request_data

    def case_time(self):
        """
            用例运行时长统计
        """
        pass

    def generate_result_data(self, test_obj=None):
        """
            生成测试报告数据
        """
        # 生成统计数据
        self.count_result(test_obj)
        # 导入模板数据进行实际测试数据的替换
        with open('../data/template_report_data.json', 'r', encoding='utf-8') as fp:
            a = fp.read()
            tmp = json.loads(a)
            # 统计数据
            tmp['testPass'] = self.test_result.pop('testPass')
            tmp['testFail'] = self.test_result.pop('testFail')
            tmp['testAll'] = self.test_result.pop('total')
            tmp['beginTime'] = self.test_result.pop('start_time')
            tmp['totalTime'] = str(self.test_result.pop('end_time')) + 's'
            tmp['testResult'] = []
            # 用例详情
            for t_index, scenes in enumerate(self.case_obj_list):
                scenes_name = list(scenes.keys())[0]
                if self.scene:
                    for case in scenes[list(scenes.keys())[0]]:
                        if case.case_pass:
                            case.case_pass = '成功'
                        else:
                            case.case_pass = '失败'
                        tmp_case = {
                            "CaseName": scenes_name + '-' + case.name,
                            "api": case.api_address.replace('http://119.3.73.53:18082/', ''),
                            "parameter": json.dumps(case.request_data),
                            "result": str(case.actual),
                            "status": case.case_pass,
                            "log": case.logs
                        }
                        tmp['testResult'].append(copy.deepcopy(tmp_case))
        with open('../report/report_data.json', 'w', encoding='utf-8') as fpw:
            fpw.write(json.dumps(tmp, ensure_ascii=False))

    def count_result(self, case_obj):
        pass_count = 0
        fail_count = 0
        for scenes in case_obj:
            pass_bool = [i.case_pass for i in scenes[list(scenes.keys())[0]]]
            pass_name = [i.name for i in scenes[list(scenes.keys())[0]]]
            merge_info = dict(zip(pass_name, pass_bool))
            if False in pass_bool:
                fail_count += 1
                self.test_result.update({list(scenes.keys())[0] + '-失败': merge_info,
                                         'testFail': fail_count})
            else:
                pass_count += 1
                self.test_result.update({list(scenes.keys())[0] + '-通过': merge_info,
                                         'testPass': pass_count})
        self.test_result['total'] = pass_count + fail_count
        loginfo.debug('\033[1;33m测试完成\n {}\033'.format(self.test_result))
        loginfo.debug('总计: {} 通过{}，失败{}'.format(self.test_result['total'], self.test_result['testPass'],
                                                self.test_result['testFail']))


class TestReport:
    """
    测试报告类，通过运行结果数据将测试结果匹配到html模板中形成报告
    """

    def __init__(self, test_data=None):
        self.report_name = setting.report['name']
        self.save_path = setting.report['report_path']
        self.test_data = test_data

    def create_report(self):
        with open('../data/template_report.html', 'r', encoding='utf-8') as fp:
            h = fp.read()
        with open('../report/report_data.json', 'r', encoding='utf-8') as fp:
            detail_report_data = json.loads(fp.read())
            detail_report_data['testName'] = self.report_name
            detail_report_data = json.dumps(detail_report_data, ensure_ascii=False)
        test_report = re.sub('\$\{(testresultData)\}', detail_report_data, h)
        with open(self.save_path, 'w', encoding='utf-8') as fp:
            fp.write(test_report)


# excel驱动-编写用例方便-用例管理比较清晰直观
def excel_driver(file):
    def run_case2(cls):
        # 生成可执行的用例对象
        if cls.scene:
            datas = TestData(file).get_scene_data()
        else:
            datas = TestData(file).get_data()
        case_obj_list = []
        for case in datas:
            if 'scene_case' in case:
                if case.get('is_run'):
                    tmp_data = []
                    for i in case['scene_case']:
                        tmp_data.append(cls(**i))
                    case_obj_list.append({case['name']: tmp_data})
            else:
                case_obj_list.append(cls(**case))
        cls.case_obj_list = case_obj_list
        return cls
    return run_case2


# py文件驱动--编写用例灵活,可使用python方法对请求参数做处理
def py_driver(api_case, scene_case):
    def case(cls):
        case_obj_list = []
        for case in scene_case:
            tmp_data = []
            if case.get('is_run'):
                for name in case['scene_case']:
                    for i in api_case:
                        if name == i.get('name'):
                            tmp_data.append(cls(**i))
                case_obj_list.append({case['name']: tmp_data})
        cls.case_obj_list = case_obj_list
        return cls
    return case


if __name__ == '__main__':
    # TestReport().create_report()
    import time
    print(datetime.datetime.now())
    start_time = datetime.datetime.now()
    time.sleep(2)
    end_time = datetime.datetime.now() - start_time
    print(start_time, end_time.seconds)